home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SPACE 1
/
SPACE - Library 1 - Volume 1.iso
/
program
/
385
/
arpbook4
/
chap_4.doc
next >
Wrap
Text File
|
1985-11-19
|
116KB
|
2,440 lines
Atari ST Machine Specific Programming In Assembly
Chapter 4: Initialization Algorithms, Part 1
The prime directive of a computer system is to function
as an algorithm executing environment. We can consider the
unblemished environment, in which there is no operating
system, no stored data and no stored applications, to be the
fundamental environment. When we add an operating system to
the fundamental environment, we inject a director of
operations which dictates to the unblemished environment the
manner in which it will fulfill its prime directive.
Initially, the operating system is established as an
unblemished subenvironment within the fundamental
environment. When we cause an algorithm to be executed
under the direction of this unblemished subenvironment, the
algorithm is placed within an execution environment that is
configured according to the operating system's prime
directives. The configuration of the execution environment
as it exists immediately after the algorithm is placed
therein imposes a set of conditions under which the
algorithm may attempt to perform its prime directives.
The set of conditions which restrict the activities of
the algorithm at the onset of execution are called initial
conditions, simply because they exist before execution
commences. These initial conditions must be distinguished
from later conditions because the configuration of the
execution environment is subject to change. In fact, the
execution environment may be altered by the algorithm
itself.
It is possible, and probable, that the algorithm's
first prime directive will consist of a set of instructions
to establish new initial conditions. These new conditions
may remove restrictions imposed by the operating system, or
they may further restrict the activities of the algorithm's
other prime directives. When an algorithm establishes new
initial conditions, we say that it initializes the execution
environment. And, as long as neither the initial conditions
imposed by the operating system, nor those imposed by the
algorithm prevent such activity, the algorithm is free to
alter existing conditions at any time during its execution.
It is via this power to alter execution environments
that we are able to establish a variety of system
configurations which we translate into system flexibility.
A flexible system is able to perform any of hundreds of
tasks equally well. As payment for this flexibility, we are
forced to maintain the execution environment of all but the
most trivial algorithms.
In chapter two, I pointed out an Assempro debugger
deficiency which forced me to develop an initialization
algorithm that would prevent a program's first line of text
output from overwriting the debugger screen. This algorithm
established initial conditions in the program's execution
environment, which had been established by AssemPro, to
correct the deficiency; it was the compromise that I had to
make with the debugger so that it would execute my program
in a manner that I considered to be proper.
While one would be justified in venting a certain
amount of annoyance because of the bother, perhaps by booing
and hissing for a few minutes, permitting this trifle to
fester into something more than a minor irritant would only
hinder productivity. This type of bother is not at all
uncommon when working with computers. However, I am sure
that you have already come to know this, even if your
experience has been limited to executing programs rather
than writing them.
I think that one can justifiably view all program
initialization algorithms as corrections to the execution
environment. It seems that one should expect a modern
operating system to handle such trivial, repetitive
programming chores. However, we might wish to consider the
probable inflexibility of such an operating system. Well,
regardless of our wishes or opinions concerning the matter,
for all but the most trivial, the code proper of each Atari
ST program must be preceded by a sequence of instructions
that establishes appropriate initial conditions before
execution proper begins. Initial conditions for the more
simple programs is the subject of this chapter.
Its lack of rigidity is what I consider to be one of
the most powerful attributes of the ST's programming
environment. Nevertheless, it is the operating systems's
flexibility which exacerbates the development of a program
classification scheme that would permit one to assign
specific initialization algorithms to specific program
types. When one decides to explain the configuration of the
ST operating system, one must discuss the interrelation of
TOS, GEM, GEMDOS, BIOS, XBIOS, GDOS, VDI, AES, Line-A and
Line-F routines. I am not even going to try to best those
that have already accomplished this task admirably. Instead
I refer you to an excellent article, Atari St Software
Development, by Michael Rothman, Byte, September, 1986. You
can obtain a copy from Byte or, perhaps, from a local
library.
Alternately, although the discussions are not nearly as
lucid, you might see what you can get out of pages 2-3
through 2-5 of the Katherine Peel book and pages 3 to 9 of
the Compute! AES book, by Leemon. If you add to that which
you obtain in knowledge from those two books, by reading
chapter 1 of the Abacus GEM Programmer's Reference, you may
be able to get by without the Rothman article.
Note this, in case you don't see it elsewhere, the
Line-F Emulator is used by the operating system, it is not
available to users as is indicated on page 237 of the
Internals book. The Internals book hints at this use on
pages 238-239, but the hint is not sufficient; the book
should warn readers that interference with this emulator
will upset the AES portion of the operating system.
Without trying to force a classification on any type of
Atari ST program, I find it convenient to discuss them in
terms of specific attributes which can be assumed from the
following set of categories:
1. The suffix appended to the program name.
2. The initialization algorithm used.
3. The manner in which execution is initiated.
4. The method by which processor control is
relinquished.
5. The algorithm used to terminate execution.
To some extent, we can classify a program as a
particular type by referring to one or more of its list of
attributes drawn from the above categories. For example, if
the suffix of a program is ACC, we call it a desk accessory.
And the unblemished operating system, as it exists at the
time I compose this sentence, expects a desk accessory to be
executed in a particular manner, to reside in a particular
environment, to contain a particular initialization
algorithm, to relinquish processor control in a particular
manner and to remain active until the computer is turned
off.
But I have one desk accessory, Multi Desk, which
creates an environment in which I can ignore most operating
system expectations for all other desk accessories,
including additional copies of itself. Therefore, if I were
to give you a list of required desk accessory attributes, I
would have to imagine all sorts of exceptions, some of which
may not even be known to me at this time. At best, all I
can do is to give you examples of programs which will
function as desk accessories in an unblemished operating
system environment, while placing little or no restrictions
on their execution in other environments.
Nevertheless, in general, suffix differentiation serves
as an indication of the operating system services required
by the program and the level of user interaction with the
program. The program suffixes recognized by the operating
system are: PRG, TOS, TTP, and ACC. You have observed some
of the differences in services that you can expect for
programs that have a PRG or TOS suffix. But, remember,
these differences were exhibited by an unblemished
environment. There is no reason to assume that identical,
or even opposite, services cannot be forced by some
environment modifying agent. Thus, when assigning
attributes to a program, we should not assume that they are
tightly bound and not subject to change. We must remain as
flexible as the computer system we are using. The best use
to which we can apply descriptives is for the convenience of
the moment. Then we shall not be surprised by behavior that
we would otherwise view as deviations from the norm. With
this viewpoint, we continue.
Initialization algorithms tend to define the execution
environment based on the task to be performed. For example,
if a program is to produce graphic output, we can suppose
that it will contain VDI initialization algorithms. And if
the graphic output is directed to a window then we can
suppose the necessity of AES initialization algorithms.
Program execution may be initiated at boot time, from
the desktop, from the menu line or by command of another
program. An example of the latter process is the execution
of a program from within a debugger. But, as I have
indicated, with the introduction of utilities such as Multi
Desk, it is no longer possible to define restrictions
concerning program execution initiation. For example, Multi
Desk permits a desk accessory to be loaded and executed
after the system has booted.
Primarily, the method of relinquishing processor
control is determined by the memory residency status of a
program. Desk accessories usually relinquish control with
AES function $17, aka evnt_mesag. Load and stay resident
(LSR) programs usually relinquish control with GEMDOS
function $31, aka ptermres. When a program causes another
to be loaded and executed, it relinquishes processor control
by executing GEMDOS function $4B.
The execution of a program may be terminated with
GEMDOS function $0, which simply passes processor control
back to the initiating agent, either the desktop or another
program. Or it may be terminated with GEMDOS function $4C,
which will pass a message back to the initiating agent. If
the program is a desk accessory, it may not terminate until
the computer is turned off, unless it has been executed via
a utility such as Multi Desk.Now, it is with an enlightened
unbiased frame of reference with which we shall proceed into
a discussion of initialization algorithms for ST programs.
Parameters for GEMDOS Function $4A
The information contained on pages 137 - 138, GEMDOS
function $4A, aka (also known as) setblock or m_shrink, and
pages 145 - 148 of the Abacus Internals book becomes germane
at this time. The discussion on page 37 of Compute!'s VDI
book is also worth reading, but there are two critical
errors on that page. SETBLOCK is a GEMDOS function, not an
XBIOS function; and the second longword on the stack, not
the second word, points to the start of the Transient
Program Area.
I would like you to suppress the portion of these
discussions pertaining to the necessity of a program
declared stack. I want you to realize that a default stack
does exist, and it is perfectly reliable for programs with
limited stack growth. The phrase limited stack growth is
somewhat vague, but I will give you examples that exhibit
such growth.
Either the Compute! or the Abacus discussion (or both)
should impress you with the following facts: one, a
program's basepage precedes the program proper; two, at the
onset of program execution, the first address of the
basepage is stored at 4(a7); three, GEMDOS function $4A
requires two parameters: the number of bytes occupied by the
program and the value that is stored in 4(a7) at the onset
of execution.
While it may seem that three parameters are passed when
function $4A is invoked, according to the examples you will
see (One is called a meaningless word in the Abacus
example.), that third parameter is simply one word of the
function specification for the GEMDOS call. If you specify
the function as a longword, it becomes $4A0000, and there is
no meaningless word to be passed. This means that you
specify the function using the instruction: move.l #$4A0000,
-(sp). I want to impress you with the fact that the
number of bytes occupied by a program is precisely the
difference between the last address occupied by the program
and that value at 4(a7). In program 9, a label is placed at
the end of the program, just before the end statement.
Then, the number of bytes occupied by the program is
calculated by subtracting the basepage start address from
the address of that label. Program 9 was written to verify
that the value obtained by this method is equivalent to that
obtained using the sum of program segments method discussed
in the references.
The verification procedure requires the use of a rather
complex binary to ASCII decimal conversion algorithm. This
subroutine is essential to the output of data, not only in
program 9 but also in the programs to immediately follow. A
discussion of program 9's algorithms follows its listing and
execution results.
Program 9. Comparing two methods of computing program
length. Execution results follow the listing.
; Program Name: PRG_3AP.S
; Assembly Instructions:
; Assemble in PC-relative mode and save with a PRG and a TOS extension.
; Function:
; Verifies that two methods of calculating the amount of memory
; occupied by a program are equivalent.
; Execution Instructions:
; Double click on PRG_3AP.TOS from the desktop. A heading and two lines
; of data will be printed on the screen. Press the return key to terminate
; execution and return to the desktop.
; MAJOR NOTE: Because base page information is needed by this program,
; if it is to be executed in the AssemPro debugger, it must
; be loaded with the "Execute program" function.
; Note - The stack used in the program is small enough to permit easy
; access to its contents via the AssemPro debugger. The stack
; exhibits limited growth, and program execution is flawless with
; a system assigned, default stack.
calculate_size_1: ; Sums data that is stored in basepage.
movea.l 4(a7), a5 ; Copy basepage address in A5.
move.l $C(a5), d7 ; Copy program text length in D7.
add.l $14(a5), d7 ; Add data length.
add.l $1C(a5), d7 ; Add bss length.
add.l #$100, d7 ; Add basepage length.
calculate_size_2: ; Subtracts last program address from first.
lea program_end, a4 ; Put "end of program" address in A4.
suba.l 4(a7), a4 ; Subtract basepage address from A4.
mainline:
lea stack, a7 ; Put address of label "stack" in A7.
bsr.s print_newline ; Prevents damage to AssemPro screen and
; skips a line for printer output.
lea heading, a0 ; Print heading for program's output. The
bsr.s print_string ; print_string subroutine expects the address
; of the string to be in A0.
print_size_1:
lea message_1, a0 ; Print label to identify output data.
bsr.s print_string ;
move.l d7, d1 ; The bin_to_decimal subroutine expects D1 to
bsr.s bin_to_dec ; contain the binary number to be converted.
lea decimal, a0 ; Print program size, an ASCII string that is.
bsr.s print_string ; stored in the array "decimal".
lea end_msg, a0 ; Print "units" label for output data.
bsr.s print_string
print_size_2:
lea message_2, a0 ; Same remarks as for "print_size_1".
bsr.s print_string
move.l a4, d1
bsr.s bin_to_dec
lea decimal, a0
bsr.s print_string
lea end_msg, a0
bsr.s print_string
wait_for_keypress: ; Halt execution so screen can be read.
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp ; Reposition stack pointer at top of stack.
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
;
; SUBROUTINES
;
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reposition stack pointer.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
; This is a binary to ASCII decimal conversion subroutine. It converts a
; 32-bit binary number in register D1 to an ASCII string that is stored in
; an array called "decimal".
; The binary to ASCII decimal conversion subroutine uses an algorithm
; based on "repeated subtraction". The subtrahends are powers of ten
; which range from 10^1 to 10^9. These values were chosen to accommodate
; up to ten digit integers. The binary value passed to the subroutine is
; the initial current minuend. After a decimal digit is extracted from
; a current minuend, a new current minuend is produced.
; During the subtraction process, the minuend is gradually reduced to a
; negative number. The number of times that each power of ten can be
; subtracted from a current minuend before the minuend becomes negative
; yields a significant digit for that power of ten.
; As each digit is extracted, it is converted to its ASCII code by adding
; the ASCII code for decimal 0 to the accumulated subtractions count. The
; ASCII code for each digit is stored as a character in a string. The string
; is accumulated in the array called "decimal".
; After the current minuend becomes negative a "new" minuend is formed by
; adding the current subtrahend to the negative value. The process
; continues until the current subtrahend is 0.
; The algorithm discards leading zeroes using a loop that subtracts
; subtrahends from the number in D1, beginning with the largest, until a
; subtrahend which does immediately yield a negative difference is detected.
; NOTE: Register D2 is the subtractions counter. In it is accumulated the
; number of times that a subtrahend can be subtracted from a minuend
; before the minuend becomes negative. At the start of the subroutine
; D2 is initialized to zero to guarantee that the first count
; accumulated is correct.
; But observe that D2 is initialized to -1 in the section of the
; routine labeled "loop_setup". The reason: after registers are
; initialized in the loop_setup section, execution branches to the
; section labeled "subtract". The first instruction in that section
; adds 1 to the value in D2.
; I chose to add 1 to the subtractions counter at that particular
; location because it was a convenient point in the algorithm to do
; so upon exit from the "discard leading zeroes" loop. Upon exit from
; that loop, a valid subtraction has already been performed, therefore,
; it must be accumulated. Since D2 contains the initialized value 0
; at that point in the execution, adding 1 puts the correct count in
; D2.
; Because I want to branch to the same section upon exit from the loop
; setup section, I must pre-initialize D2 to -1. Then, after 1 is
; added to D2 via the first instruction in the subtract section, the
; subtractions counter is fully initialized to zero, as it should be.
bin_to_dec:
lea decimal, a0 ; Put address of array "decimal" in A0.
lea subtrahend, a1 ; Put address of subtrahend table in A1.
move.l (a1)+, d0 ; Put first subtrahend in D0.
moveq #0, d2 ; Initialize subtractions counter to zero.
get_sign:
tst.l d1 ; Is binary number positive, negative or zero?
beq.s zero_passed ; Branch if number is 0.
bpl.s positive ; Branch if positive.
move.b #$2D, (a0)+ ; Store a minus sign in array "decimal".
neg.l d1 ; Change binary number from neg to pos.
bra.s discard_leading_zeroes
positive: ; Branch to here when number is positive.
move.b #$20, (a0)+ ; Store a space in array decimal.
discard_leading_zeroes: ; Subtract subtrahend from minuend.
sub.l d0, d1 ; Loop till difference is positive,
bpl.s subtract ; indicating that digit is not zero.
add.l d0, d1 ; Restore minuend.
move.l (a1)+, d0 ; Get next subtrahend.
bra.s discard_leading_zeroes
subtract:
addq.b #1, d2 ; Increment subtractions counter.
sub.l d0, d1 ; Subtract subtrahend from D1.
bpl.s subtract ; Loop until D1 becomes negative.
convert_to_ascii:
addi.b #$30, d2 ; Converts binary number to ASCII code.
move.b d2, (a0)+ ; Store the ASCII digit in array "decimal".
loop_setup:
add.l d0, d1 ; Restore the minuend.
moveq #-1, d2 ; Pre-initialize subtractions counter to -1.
move.l (a1)+, d0 ; Get next subtrahend.
bne.s subtract ; Loop back until subtrahend = 0.
move.b #0, (a0) ; Terminate decimal string with a null.
rts
zero_passed:
move.b #$20, (a0)+ ; Store a space in array "decimal".
move.b #$30, (a0)+ ; Store an ASCII zero in array "decimal".
move.b #0, (a0) ; Terminate ASCII decimal string with a null.
rts
data
subtrahend: dc.l $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710,$3E8
dc.l $64,$A,$1,$0
heading: dc.b $D,$A,'PRG_3AP Execution Results',$D,$A,$D,$A,0
message_1: dc.b ' Program Size by Method One: ',0
message_2: dc.b ' Program Size by Method Two: ',0
end_msg: dc.b ' bytes.',$D,$A,0
newline: dc.b $D,$A,0
bss
align ; Align storage on a word boundary.
decimal: ds.l 3 ; Output buffer, NULL terminated.
ds.l 24 ; Program stack.
stack: ds.l 1 ; Address of program stack.
program_end: ds.l 0
;
; NOTE: The declaration following the label "stack", above, can be
; ds.l 0, instead of ds.l 1. I take advantage of this in later
; programs. When the declaration is made in that manner, however,
; the label "program_end" does not appear in the debugger's
; output field. Instead, the label "stack" appears where you
; would expect "program_end", because, although the assembler
; accepts this label without issuing an error report, the label
; "program_end" will not be a part of the program whenever the
; declaration following "stack:" is ds.l 0.
end
PRG_3AP Execution Results
Program Size by Method One: 728 bytes.
Program Size by Method Two: 728 bytes.
Discussion of Program 9
Notice that I have declared a stack for the program. I
have done so because I want you to be able to view the
contents of the stack in the AssemPro debugger. Further,
notice that it is of a size that is commensurate with
necessity. Of course you can declare a stack size that
represents the wildest possible guess, but there is no
reason to do so.
The system functions, GEMDOS $8 and GEMDOS $9 are
adequately covered in the Abacus Internals book, however,
there are two things I must say about the code for function
$9 as it appears in that book. If a program in which this
function is invoked is to be assembled in PC-relative mode,
the address of the string must be pushed onto the stack with
the pea instruction; the move.l #label, -(sp) instruction
can be used if the program is to be assembled in Relocatable
mode. Also, note that text is an AssemPro reserved word, so
don't use it as a label.
There is a word of caution to be observed when using
GEMDOS function $9 as a subroutine. To do this, you might
be tempted to push the address of the string onto the stack
before each call to the subroutine. That will not work
because the stack and its pointer will be altered by the
call to the subroutine; the return address will be pushed
onto the stack after your string address. The way to use
GEMDOS function $9 as a subroutine is as follows: change the
first function statement to pea (An), where An is any
address register (other than A7, of course); then, just
before you call the subroutine to print a string, use the
instruction, lea string, An.
My discussion of PRG_3AP will proceed much more
smoothly if I refer to real addresses and data. Therefore,
I will use a partial disassembly of the program's basepage,
the front end of the program and the program stack, as it
appeared in the AssemPro debugger when I executed the
program. You can perform your own observations after
assembling PRG_3AP and saving it with a TOS or PRG
extension. Load it into the debugger with the Execute
program function.
You can obtain your own disassembly listing by clicking
on the Disassembling option under the Debugger menu. Figure
4.1, a drawing of the Disassembling dialog box, illustrates
the appearance of the dialog box fields, just before you
click on the OK button. If you would rather have a hard
copy, then click on PRT: instead of File :, but sending a
disassembly listing directly to a printer is hazardous
because the listing usually contains ASCII codes that will
disrupt the printer. Furthermore, you must be cautious when
you decide to use the File option also.
If you have chosen to check the Back-up copy option, as
illustrated in figure 2.4B of chapter two, then, if you now
choose a file name for the disassembly listing that is
already in use on disk, you will be presented with the File
already exists ! dialog box shown in figure 2.2C of chapter
two. But no matter which option you choose the system will
freeze, and you will have to reboot. Avoid the use of
duplicate file names when using AssemPro's Disassembly mode.
If you choose a file in which AssemPro can save the
disassembly listing, you can easily remove the ASCII codes
that disrupt the printer from the file with the Tempus
editor's Search and replace function, thereby assuring
smooth printer operation when you print the file. After
AssemPro has completed the disassembly, load the file into
the Tempus editor.
First, delete the Atari Logo from the first line of the
listing. Then, on the Search below string: parameter line
of the Search and replace dialog box, type ;*. Type nothing
on the ...and replace by following parameter line. Select
whole text as the search region, Start search at cursor,
Direction down, Quantity total. When you click the Start
button in the dialog box, Tempus will discard the semicolon
and all characters after it on all lines containing same;
this action will remove all characters which might cause the
printer to scream and beg for mercy.
Figure 4.1. The Disassembling dialog box; a slightly
compressed representation. The from and to address that you
use must be obtained from your debugger window. WARNING: Do
not use a file name that is already in use on disk.
The from address field requires the address from which
disassembly is to begin; in this case, the basepage start
address, calculated by subtracting $100 from the program
start address, as it is shown in the debugger window. The
to address field requires the address at which disassembly
is to cease; neither the to address nor its content will be
disassembled.
To determine a suitable to address, you must have some
knowledge about the last address you want disassembled.
Fortunately, we have established a program_end address,
therefore, we simply look for the value being loaded into
register A4 (after consulting out source listing) as the
first instruction in the calculate_size_2 algorithm. On my
debugger screen, this value was $86080.
Now, we think a little. If we want to see the
program_end address and the longword contents at that
address in the disassembly listing, then we must use an
address beyond that one for the to address. We can simply
add four to $86080 to obtain $86084 as the to address.
Alternately, if we decide that we don't need the program_end
address to show up on the listing, we can simply use it as
the to address. Actually, that is the second benefit we
derive from our use of the program_end label.
Figure 4.2 is a listing of the first 28 bytes of
PRG_3AP's basepage. The first address shown, $085DA8, is
the basepage start address. This is the address that is
copied from 4(A7) to A5 as the first step in the
calculate_size_1 algorithm. The basepage start address is
just $100 bytes before the program start address. The
contents of the basepage start address is itself, $00085DA8.
Within the calculate_size_1 algorithm, the basepage length,
$100 bytes, is added to data located at specific offsets
from the basepage address.
The text length is the longword located at basepage
start address + $C = $085DB4; it consumes $000000CE bytes.
The data length is the longword located at basepage start
address + $14 = $085DBC; it consumes $0000009A bytes.
Finally, the bss length is the longword located at basepage
start address + $1C = $085DC4 and consumes $00000070 bytes.
The total length accumulated by the first algorithm is $100
+ $CE + $9A + 70 = $2D8 = 728 (decimal) bytes.
Figure 4.2. Partial disassembly of program 9's basepage.
PRG_3AP.TOS disassembly after execution.
085DA8 0008 DC.W 8
085DAA 5DA8000F SUBQ.L #6,$F(A0)
085DAE 8000 OR.B D0,D0
085DB0 0008 DC.W 8
085DB2 5EA80000 ADDQ.L #7,0(A0)
085DB6 00CE DC.W $CE
085DB8 0008 DC.W 8
085DBA 5F760000 SUBQ.W #7,0(A6,D0.W)
085DBE 009A00086010 ORI.L #$86010,(A2)+
085DC4 00000070 ORI.B #$70,D0
The values used to calculate the total length by the
calculate_size_2 algorithm can be seen in figure 4.3.
Although there are no labels in that figure to guide us, we
can determine which addresses contain the data we for which
we are looking by referring to the program source code.
As shown at memory address $085EBE, the address that is
loaded into A4, $86080, is the address of the label
program_end. The basepage start address is subtracted from
that address; as shown at memory address $085EC2. The first
address shown in figure 4.3, 085EA8, is the program start
address. We can deduce the basepage start address from this
knowledge because it is $100 less, which is $085DA8.
A programmer's calculator proves to us that the
program_end address minus the basepage start address =
$86080 - $85DA8 = $2D8 = 728 (decimal). This is precisely
the value obtained by the first algorithm. We conclude that
the algorithms produce identical results, therefore we will
choose to use the second algorithm, whenever possible,
because it is faster and consumes less memory.
Figure 4.3. Disassembled front end of program 9.
085EA8 2A6F0004 MOVEA.L 4(A7),A5
085EAC 2E2D000C MOVE.L $C(A5),D7
085EB0 DEAD0014 ADD.L $14(A5),D7
085EB4 DEAD001C ADD.L $1C(A5),D7
085EB8 DEBC00000100 ADD.L #$100,D7
085EBE 49FA01C0 LEA $86080(PC),A4
085EC2 99EF0004 SUBA.L 4(A7),A4
085EC6 4FFA01B4 LEA $8607C(PC),A7
The final item of datum to be extracted from figure 4.3
is the address of the program stack, shown as the value
being loaded into register A7 at memory address 085EC6.
Figure 4.4A is a view of the entire stack. The first
address in that figure, 08600E, is not part of the stack;
the first word of data there, 4 zeroes, was inserted by the
align statement; at address 08010 is the contents of the
buffer decimal, in which we stored the results computed by
the binary to decimal conversion subroutine. You can see
the ASCII value for the leading blank space that indicates a
positive value, followed by the ASCII values for 728. By
now, you are aware that the instructions at addresses 08600E
and 086012, into which those ASCII values just happen to
translate, are happenstance.
Figure 4.4A. Program 9's stack, disassembled.
NOTE: $86010 is the address of the label "decimal". The
space reserved for decimal is three longwords. Therefore,
the last byte of the stack is at address $8601C.
Apparently, the last word of the stack that was used is at
address $86040 (The contents there is $0005.). If so, the
rest of the space declared for the stack is wastage.
08600E 00002037 ORI.B #$37,D0
086012 32380000 MOVE.W 0,D1
086016 00000000 ORI.B #0,D0
08601A 00000000 ORI.B #0,D0
08601E 00000000 ORI.B #0,D0
086022 00000000 ORI.B #0,D0
086026 00000000 ORI.B #0,D0
08602A 00000000 ORI.B #0,D0
08602E 00000000 ORI.B #0,D0
086032 00000000 ORI.B #0,D0
086036 00000000 ORI.B #0,D0
08603A 00000000 ORI.B #0,D0
08603E 00000005 ORI.B #5,D0
086042 F0C2 DC.W $F0C2
086044 0310 BTST D1,(A0)
086046 0008 DC.W 8
086048 0005F0C2 ORI.B #-$3E,D5
08604C 0314 BTST D1,(A4)
08604E 0008 DC.W 8
086050 5F0C SUBQ.B #7,A4
086052 00000000 ORI.B #0,D0
086056 FFFF DC.W $FFFF
086058 FFFF DC.W $FFFF
08605A 00000000 ORI.B #0,D0
08605E 00000000 ORI.B #0,D0
086062 00000000 ORI.B #0,D0
086066 00000000 ORI.B #0,D0
08606A 000002D8 ORI.B #-$28,D0
08606E 0008 DC.W 8
086070 607A BRA.S $860EC
086072 0008 DC.W 8
086074 5FA2 SUBQ.L #7,-(A2)
086076 00000000 ORI.B #0,D0
08607A 00000000 ORI.B #0,D0
08607E 00000000 ORI.B #0,D0
NOTE: Initially, before there is any stack movement,
$8607C is the top of the stack. The space that was
reserved with the ds.l 1 following the label "stack" is not
used because the stack address is always pre-decremented
before movement to the stack occurs. To avoid that waste,
you can simply declare ds.l 0 after the label "stack".
Now, unless we were to view the stack in the AssemPro
debugger after single stepping through each instruction (By
the way, something you don't do is single step through
system trap calls. Oops. I should have said that I have not
been successful in doing so.) we can not be certain that the
entire stack was never utilized by the program. However,
looking at the memory locations towards the bottom of the
stack (those nearest address 08601C), we see that the last
nine longwords of the stack contain zeroes after program
execution, and these locations appear not to have been used.
We could test the theory by declaring the stack to be
15 longwords instead of 24, then by assembling and executing
the program again. If our data written to the address of
the label decimal were to be preserved after that execution,
we could conclude that a stack length of 15 longwords (60
bytes) is sufficient for this program. In the references,
you will often see unreasonably long stacks being declared.
I simply remind you that a byte wasted is a byte you can't
use.
A clearer picture of stack penetration is obtained if
the variable decimal is loaded with #'s and the stack is
loaded with !'s before execution. See figure 4.4B. There
you can see that address $86040 was definitely the last
stack location used during program execution.
Figure 4.4B. A clear picture of program 9's stack
penetration.
After modifying program 9's stack declaration to be 15
longwords, the after-execution picture shown in figure 4.4C
develops. The first longword of decimal contains the ASCII
code for the new program length; the second contains a null
character and 3 #'s; the third contains 4 #'s. In
preparation for this picture, I also stored ampersands in
the longword declared following the label stack in order to
more clearly delineate the 15 longwords of the stack.
Figure 4.4C clearly shows that this new stack size if
sufficient.
Suppose that it were not enough. How could we tell?
Depending on what values were overwritten into the area
reserved for decimal, an insufficient stack size might not
be apparent. Therefore, until you become accustomed to the
stack requirements of the operating system, it is best to
start with a large stack and reduce its size after you get
some idea of the penetration. What is large? Is 512
longwords sufficiently large? That is 2048 bytes, and it is
large. When you are truly nervous, use 1024 longwords.
Perhaps it has occurred to you by now that something has
been left unsaid. A proper question at this time would be:
"Why can't we tell what the stack penetration is from the
program itself?". As you shall see when the supervisor mode
is discussed in chapter 5, a program's stack is used by
agents outside of the program also. And the penetration by
the outside agents is not easily predetermined.
Figure 4.4C. A picture of the reduced stack's
penetration. Also shown is the longword declared at the
label stack. It is filled with ampersands.
Using figure 4.4C as a guide, it is easy to see that if
the stack size were 14 longwords, instead of 15, then the
data occupying the longword at address $8601C would simply
be stored at address $86018, and we would notice no
detrimental effects because the data in decimal does not
extend far enough to occupy that longword location. In
fact, even if the stack were only 13 longwords, the program
would report the correct value for decimal because, although
stack data would occupy a key byte in the variable, that
byte of stack data, a null, is identical to the variable
datum which it would replace.
So, it is not until the size of the stack is reduced to
as few as 12 longwords, at which time the leading blank
space for the value 692 would become null, that the variable
decimal actually becomes corrupted enough to give an invalid
value as output data. Of course, if we declare the stack
space in words or bytes, corruption would take place as soon
as the null at the end of the ASCII string 692 were replaced
by the 05 in the stack longword 00 05 F0 C2.
As the declared space for the stack is reduced further,
the data in the stack overwrites more of the program's
declared data. Soon it begins to overwrite the declared
strings, and the problem becomes readily apparent simply by
looking at the programs output statements. Although stack
penetration for this program could never be deep enough to
completely disrupt the object code, even if a zero stack
size were declared, it is possible for such to occur when
the declared stack size if insufficient.
When that happens, the program may freeze the system,
bombs may appear on the screen or, if the program is
executed from the debugger, you may see a Bus error message.
Whenever you have declared a stack size that is just
sufficient for maximum penetration, suspect that stack size
immediately if you see bombs from the desktop, or if the
system freezes. It is easy enough to declare a large stack
size while you are experimenting with the program, then to
reduce the size to something more reasonable after the
program is functional.
The Binary to ASCII Decimal Conversion Algorithm
To me, it seems reasonable, even mandatory, for a
beginning assembly language programmer to question the
necessity of conversion algorithms that convert a number
from one base to another, and thence to ASCII character
codes, before printing its value. The answer involves the
functions that are used for printing and the parameters
which they expect to be passed to them. The phrases of
interest can be viewed on pages 107 - 110 of the Internals
book.
Within the explanations of the functions discussed on
those pages, you see phrases such as The ASCII code of the
pressed key is returned in the low byte of the low word and
The ASCII value of the character to be printed must be in
the low byte of the low word. Now, these discussions tell
us nothing about converting from one base to another, nor do
they tell us how to produce the ASCII characters that the
functions find so desirable.
The example of function $02 usage, on page 108, simply
indicates that the decimal number 65, passed as a parameter
will produce the letter A on the video screen. But suppose
we want to print the number 65? Then we would have to call
the function twice: once with the ASCII value 54, then
again, with the ASCII value 53. Using the GEMDOS $02
function in this manner, we can directly print any character
that we wish to the screen. However, this usage is of
minimal value to us.
We need a way to pass parameters to these functions
without this direct involvement in the passing of ASCII
coded parameters. The binary to ASCII decimal conversion
algorithm relieves us of this burden. The algorithm does
more than that. When we accumulate a number to be printed,
the result is stored in some register as a binary value. We
could choose to print this binary value, if we so desired.
However, we would still need to convert each binary digit to
its ASCII equivalent before passing it to the output
function.
Doing that, we would end up with a long string of ones
and zeroes on the screen. We don't really want that; we
want to see something much easier to interpret. That's why
we want to convert the number from binary to decimal. Well,
the conversion algorithm does that also. In a two-stage
process, the algorithm first converts the binary number to
decimal digits, then it converts the decimal digits to ASCII
characters.
The conversion algorithm is amply documented within the
program listing, so I shan't repeat the information here. I
will say that this binary to ASCII decimal algorithm is not
the one you will see most often in the references.
Therefore, this algorithm will be compared to the one you
are most likely to see in an upcoming program.
I find the fact that all of this repetitive conversion
is still required on modern computers to be disturbing. Yet
there is little that we can do about it at this time, except
to develop conversion algorithms that execute as rapidly as
possible, while consuming as little memory as possible. In
this spirit, I offer a modification to the bin_to_dec
algorithm that reduces the execution time.
Change the statement moveq #0,d2 (just before the
get_sign label) to move.b #$30,d2. This new statement
initializes the subtractions counter to the ASCII value for
decimal 0. When the counter is incremented for a valid
subtraction, the ASCII code in D2 will be valid for the
accumulated count. If the number passed to the subroutine
is zero, then the ASCII code for that value will already be
in D2 when the branch is made to the zero_passed label;
therefore, you can change the second statement at that label
from move.b #$30,(a0)+ to move.b d2,(a0)+.
In the loop_setup section of the algorithm, change the
moveq #-1, d2 statement to move.b #$2F,d2 to initialize the
subtractions counter to 1 less than the ASCII code for
decimal 0. Delete the label convert_to_ascii: and the
instruction addi.b #$30,d2 immediately following it. This
conversion is no longer necessary because it is the ASCII
code for the decimal digits that is being accumulated in D2.
The Programs That Use GEMDOS Function $4A
At times, and remember that I consider it to be the
most powerful available for any computer, the behavior of
the ST's operating system resembles that of a newborn child
which requires burping after a feeding. This seems to be
the role of GEMDOS function $4A (aka m_shrink, setblock and
etc.). Any program, except those that are desk accessories
and those that relinquish processor control with GEMDOS
function $31, which does not begin with an initialization
sequence that invokes this function ties up all available
memory.
That this is a default situation is appalling. Could
we not rely on the loader to calculate the memory occupied
by a program that it has just loaded and to assign to that
program the memory required for its occupancy? The subject
of ST memory management is adequately covered in the
Internals book, and I have no reason to repeat its
information. Frankly, I would find the task depressing
because the part of the operating system that performs this
function is so damn inefficient.
I have included program 10 as a well documented example
which features the use of function $4A. You should read the
MAJOR NOTE included in the program before attempting to
assemble and execute the program. To verify that this
program functions correctly, do this: load PRG_3BP.TOS into
the AssemPro debugger using the Execute program function;
try to select the Save screen function--AssemPro will tell
you that there is insufficient room in memory; install a
breakpoint at the lea $C(A7),SP instruction and click on the
Run program button; when execution stops at the breakpoint,
you will now be able to select the Save screen function
because the invocation of GEMDOS function $4A will have
released all memory not occupied by the program.
Program 10. A program that initializes with GEMDOS function
$4A.
; Program Name: PRG_3BP.S
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Program Function:
; Illustrates the use of GEMDOS function $4A to return to GEMDOS all
; memory except that required by this program.
; Execution Instructions:
; Double click on PRG_3BP.TOS from the desktop. After viewing the
; program's output, press the Return key to terminate execution.
; Because base page information is needed by this program, if it is to
; be executed in the AssemPro debugger, it must be loaded with the
; "Execute program" function.
; MAJOR NOTE:
; Whenever a program which processes base page information is to be
; executed in the AssemPro debugger, special attention must be paid to the
; process by which the program is loaded into the debugger.
; If the program is loaded into the debugger with the "Execute program"
; function, the entire program can be executed.
; If, however, the program is loaded into the debugger as a result of
; just having been assembled, the instructions which process basepage
; information cannot be executed because there is no basepage when a program
; is loaded by that process.
; For this particular program you would procede as follows: relocate the
; program by clicking on the Relocate button, then execute one, and only
; one, of the initialization instructions. That is instruction four of the
; program:
;
; lea stack, a7
; To accomplish this, move the PC cursor in the disassembly output field
; to the fourth instruction, and execute the instruction by single stepping.
; Then, move the PC cursor to the line containing the label "mainline".
; Now, execution may proceed via the "Run program" or "Single step" buttons.
; A program that used a default stack provided by the system would
; require only that the PC cursor be moved beyond the initialization
; sequence, which includes the return_memory algorithm.
calculate_program_size:
lea program_end, a0 ; Put "end of program" address in A0.
movea.l 4(a7), a1 ; Put basepage address in A1.
suba.l a1, a0 ; Subtract basepage address from program's
; ending address.
lea stack, a7 ; Point A7 to this program's stack.
return_memory: ; Return unused memory to operating system.
move.l a0, -(sp) ; Store total program length in stack.
move.l a1, -(sp) ; Store basepage address in stack.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A0000.
trap #1 ; GEMDOS call.
lea $C(a7), sp ; Reset stack pointer to top of stack.
; Note: When the stack pointer must be moved 8 bytes or less, use
; addq.l #n, sp, where n is the number of bytes. When it must
; be moved more than 8 bytes, addq.l can't be used.
; Although you may be tempted to use adda.l #n, sp in that case, a
; better choice is lea n(a7), sp because it is twice as fast and
; requires 2 bytes less of memory. Program LEA_ADDA.TOS verifies
; these claims.
mainline: ; Marks the beginning of program proper.
lea newline, a0
bsr.s print_string
; The above instructions prevent damage to the debugger screen and skip a
; line for printer output. Skipping a line on the printer separates the
; program output from a listing which precedes execution, or it will
; separate the results of repeated executions.
print_declared_string:
lea string, a0 ; Put address of the label "string" in A0.
bsr.s print_string
wait_for_keypress:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1
addq.l #2, sp
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1
; SUBROUTINES
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1
addq.l #6, sp
rts
data
newline: dc.b $D,$A,0 ; All strings must be NULL terminated because the
; function we are using to print them requires it.
; Note that the ASCII code for a NULL character is $0,
; which is equal to decimal 0.
; The ASCII code for a carriage return is $D; that for
; a linefeed is $A.
string: dc.b 'This string will not overwrite the AssemPro debugger screen.'
dc.b $D,$A,0 ; The string is continued on this line. Here, we
; declare a carriage return, linefeed and terminate
; the entire string with a NULL = $0 = 0.
bss
align ; Align storage on a word boundary.
ds.l 16 ; Stack.
stack: ds.l 0 ; Address of stack.
program_end: ds.l 0 ; Marks the end of program memory.
end
Repeated Subtraction Versus Repeated Division
The binary to ASCII decimal conversion algorithm that
you are most likely to see in references is based on
repeated division by decimal 10. Since I advocate the use
of a repeated subtraction algorithm, similar to the one
introduced in program 9, it seems reasonable to expect me to
present a program that justifies my impudence (that's ud
back there, not ot).
Program 11 is a two-part program that verifies the
identical accuracy of two binary to ASCII decimal conversion
algorithms in the first part, then goes on to compare their
execution speeds. The time required to execute each
algorithm is too short for accurate measurement, therefore,
each algorithm is executed 1000 times. The times reported
by the program are the elapsed times for those 1000
executions.
The Get_time Algorithm
I think that you shall find the program sufficiently
documented, but the get_time subroutine deserves special
attention. As you read about GEMDOS function $20 on pages
117 - 118 of the Internals book, compare the code there to
that in program 11, and you will see that I did not find it
necessary to save the original value of the supervisor stack
pointer. This is so because the content of D0 was not
altered between the two function $20 calls which are
required.
As you study other GEMDOS functions, you will see one
labeled Get Time (GEMDOS function $2C), and you may wonder
why that function was not used in this program. Then,
later, you will probable run across the BIOS function
Tickcal and wonder what it has to do with time, if anything.
Well, the answers are simple. GEMDOS function $2C has a
resolution of 2 seconds, which is too low for accurate
execution speed measurement. As promising as the Tickcal
function might appear to be at first sight, it actually does
nothing but return the value 20 milliseconds every time you
call it.
Fortunately, we are able to access an ST system
variable at memory location $4BA. This variable is called
_hz_200, and it is described in the Internals book as being
the Counter for 200 Hz system clock; in the Peel book it is
described as being the Raw 200Hz timer tick. We gain access
to this system variable by invoking GEMDOS function $20 to
place the microprocessor in the supervisor mode. Refer to
the subroutine within program 11 for details of my get_time
algorithm's operation.
Program 11. Comparison of two binary to ASCII decimal
conversion algorithms.
; Program Name: PRG_3CP.S
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS extension.
; Execution Instructions:
; Execute from the desktop. Terminate execution by pressing the Return
; key.
; Function:
; This program is divided into two parts. Part 1 verifies that the
; results from two binary to ASCII decimal conversion algorithms are
; identical. The first conversion algorithm is called the "repeated
; division" method; the number to be converted is repeatedly divided by 10.
; The second algorithm is called the "repeated subtraction" method; powers
; of ten are repeatedly subtracted from the number to be converted.
; Part 2 compares the two algorithms to determine which is the faster.
; Part 1 contains three sections. Section 1 confirms that both algorithms
; yield the same result for a positive number; section 2 confirms identical
; results for a negative number; section 3 confirms identical results for
; the number zero.
; A FEW NOTES:
; 1 - "clr.w Dn" is one of the fastest ways to clear only the lower word
; of a data register.
; 2 - The stack used in the program is small enough to permit easy access
; to its contents via the AssemPro debugger.
; 3 - In the "repeated division" algorithm, a null character must be stored
; in the array "reversed" for proper operation of the
; "reversed_to_decimal" loop when the program is executed from AssemPro.
; This is true because AssemPro will not necessarily clear the array to
; zeroes.
; When the program is executed from the desktop, however, the operating
; system will clear the array. Therefore, if the program were intended
; for use such as it is, the instruction which stores the null at the
; end of the array "reversed" could be eliminated.
; This is only one of the many types of adjustments
; which must be made when executing programs from debuggers.
calculate_program_size:
lea program_end, a0 ; Put "end of program" address in A0.
movea.l 4(a7), a1 ; Put "basepage" address in A1.
suba.l a1, a0 ; Subtract basepage address from A0.
lea stack, a7 ; Point A7 to this program's stack.
return_memory: ; Return unused memory to operating system.
pea (a0) ; Store total program length in stack.
pea (a1) ; Store basepage address in stack.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
; NOTE: The above instruction is a combination of two that are often seen
; in references:
; move.w d0, -(sp) ; Dummy value, can be anything.
; move.w #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
trap #1 ; GEMDOS call.
lea $C(sp), sp ; Reset stack pointer to top of stack.
mainline: ; Marks the beginning of program proper.
lea heading, a0 ; Print heading for program's output.
bsr print_string
;
; PART 1, SECTION 1: Conversion of a positive binary number to ASCII decimal.
;
lea part_1_head, a0 ; Print PART 1 heading.
bsr print_string
lea sect_1_head, a0 ; Print SECTION 1 heading.
bsr print_string
repeated_division_1:
lea division_head, a0 ; Print heading for division results.
bsr print_string
move.l #2147483647, d1 ; Number to be converted must be in D1.
bsr bin_to_dec_1 ; ASCII decimal string stored in "decimal".
lea decimal, a0 ; Print ASCII decimal string.
bsr print_string
; NOTE: Remember, although the number we store in D1 appears to our eyes
; to be a very familiar decimal number, the computer does not see
; it that way. It is the assembler that lets us see things that
; are palatable to us while we are programming, and which, during
; assembly, converts that which we like to something the computer
; likes. And the computer likes binary.
repeated_subtraction_1:
lea subtract_head, a0 ; Print heading for subtraction results.
bsr print_string
move.l #2147483647, d1 ; Number to be converted must be in D1.
bsr bin_to_dec_2 ; ASCII decimal string stored in "decimal".
lea decimal, a0 ; Print ASCII decimal string.
bsr print_string
bsr print_newlines
;
; PART 1, SECTION 2: Conversion of a negative binary number to ASCII decimal.
;
lea sect_2_head, a0 ; Print SECTION 2 heading.
bsr print_string
repeated_division_2:
lea division_head, a0 ; Print heading for division results.
bsr print_string
move.l #-7483647, d1
bsr bin_to_dec_1
lea decimal, a0
bsr print_string
repeated_subtraction_2:
lea subtract_head, a0 ; Print heading for subtraction results.
bsr print_string
move.l #-7483647, d1
bsr bin_to_dec_2
lea decimal, a0
bsr print_string
bsr print_newlines
;
; PART 1, SECTION 3: Conversion of binary number zero to ASCII decimal.
;
lea sect_3_head, a0 ; Print SECTION 3 heading.
bsr print_string
repeated_division_3:
lea division_head, a0 ; Print heading for division results.
bsr print_string
move.l #0, d1
bsr bin_to_dec_1
lea decimal, a0
bsr print_string
repeated_subtraction_3:
lea subtract_head, a0 ; Print heading for subtraction results.
bsr print_string
move.l #0, d1
bsr bin_to_dec_2
lea decimal, a0
bsr print_string
bsr print_newlines
;
; PART 2: Repeated division algorithm versus repeated subtraction algorithm
; execution speed comparision. Each algorithm is executed 1000 times.
;
lea part_2_head, a0 ; Print PART 2 heading.
bsr print_string
repeated_division_method:
lea div_time_head, a0
bsr print_string
move.l #9999, d7 ; D7 is counter for the push loop.
bsr get_time ; Value of system clock returned in D5.
move.l d5, d6 ; Save time in scratch register.
division_loop:
move.l #1928374650, d1 ; Number to be converted to ASCII decimal.
bsr bin_to_dec_1
dbra d7, division_loop ; Loop 1000 times.
bsr get_time ; Get current value of system clock.
sub.l d6, d5 ; Subtract beginning value from ending value.
mulu #5, d5 ; Convert to milliseconds.
move.l d5, d1 ; Transfer value for conversion to ASCII decimal.
bsr bin_to_dec_2 ; Convert.
lea decimal, a0 ; Print repeated division algorithm time.
bsr.s print_string
lea units_label, a0 ; Print time label.
bsr.s print_string
repeated_subtraction_method:
lea sub_time_head, a0
bsr.s print_string
move.l #9999, d7 ; D7 is counter for the push loop.
bsr get_time ; Value of system clock returned in D5.
move.l d5, d6 ; Save time in scratch register.
subtraction_loop:
move.l #1928374650, d1 ; Number to be converted to ASCII decimal.
bsr bin_to_dec_2
dbra d7, subtraction_loop; Loop 1000 times.
bsr get_time ; Get current value of system clock.
sub.l d6, d5 ; Subtract beginning value from ending value.
mulu #5, d5 ; Convert to milliseconds.
move.l d5, d1 ; Transfer value for conversion to ASCII decimal.
bsr bin_to_dec_2 ; Convert.
lea decimal, a0 ; Print repeated division algorithm time.
bsr.s print_string
lea units_label, a0 ; Print time label.
bsr.s print_string
wait_for_keypress:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp ; Reposition stack pointer at top of stack.
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
;
; SUBROUTINES
;
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reposition stack pointer.
rts
print_newlines: ; Prints 2 carriage returns and linefeeds.
pea newlines ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
; Get_time subroutine. Returns current value of system clock in D5.
; The get_time subroutine obtains the current value of the system variable
; _hz_200 (memory location $4BA). This variable is incremented every 200
; hertz, which means that the period between increments is 5 milliseconds
; (1/200 = .005). In turn, this means that the variable measures time with
; a resolution of 5 milliseconds.
; Use this subroutine to measure the elapsed time of an event as follows:
; 1. Read and store the content of $4BA at the beginning of the event.
; 2. At the conclusion of the event, read the content of $4BA again.
; 3. Subtract the first value from the second. This yields the number of
; 5 millisecond intervals which occurred during the event.
; 4. Multiply the difference by 5 to convert the elapsed time to milliseconds.
get_time: ; Get number of 5 msec periods.
move.l #0, -(sp) ; The zero turns on supervisor mode.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Go to supervisor mode.
addq.l #6, sp ; Supervisor stack pointer returned in D0.
move.l $4BA, d5 ; Copy system time into register D5
move.l d0, -(sp) ; Restore supervisor stack pointer.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Go to user mode.
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
; The binary to ASCII decimal conversion subroutine uses an algorithm
; based on the "repeated division" algorithm discussed in chapter 9 of the
; Ford & Topp book; however, the algorithm used here is not limited to a
; 16-bit binary number. There is a similar algorithm in the Atari section
; of appendix B in the Skinner book. The divisor is decimal 10.
bin_to_dec_1: ; Converts 32-bit binary number in D1 to
; ASCII decimal.
lea decimal, a0 ; Point to beginning of array "decimal".
lea reversed + 10, a1 ; Point to end of array "reversed".
move.b #0, (a1) ; Put a null at the end of the array.
_get_sign:
tst.l d1 ; Is binary number positive, negative or zero?
beq.s _zero_passed ; Branch if number is 0.
bpl.s _positive ; Branch if positive.
move.b #$2D, (a0)+ ; Store a minus sign in array decimal.
neg.l d1 ; Change number from negative to positive.
bra.s _division_loop
_positive: ; Branch to here when number is positive.
move.b #$20, (a0)+ ; Store a space in array decimal.
_division_loop:
move.w d1, d2 ; Store lower word in temp register D2.
clr.w d1 ; Clear lower word.
swap d1 ; Move higher word to lower word.
divu #10, d1 ; Divide full 32 bits by ten.
move.w d1, d3 ; Store quotient in temp register D3.
move.w d2, d1 ; Combine lower word with remainder.
divu #10, d1 ; Divide full 32 bits by ten.
swap d1 ; Swap quotient and remainder words.
_convert_to_ascii: ; Convert digit to ASCII and store it.
addi.b #$30, d1 ; Convert digit to ASCII.
move.b d1, -(a1) ; Store the digit in array "reversed".
move.w d3, d1 ; Bring in higher word quotient.
swap d1 ; Swap high and low word quotients.
tst.l d1 ; Is content of D1 zero yet?
bne.s _division_loop ; Continue until content of D1 is zero.
reversed_to_decimal: ; Transfer contents of "reversed" to "decimal".
move.b (a1)+, (a0)+ ; Loop until the null is transfered.
bne.s reversed_to_decimal
rts
_zero_passed:
move.b #$20, (a0)+ ; Store a space in array "decimal".
move.b #$30, (a0)+ ; Store the zero in array "decimal".
move.b #0, (a0) ; Terminate the decimal string with a null.
rts
; Conversion from binary to ASCII decimal using repeated subtraction.
; See documentation in program PRG_3AP.S.
bin_to_dec_2:
lea decimal, a0 ; Put address of array "decimal" in A0.
lea subtrahend, a1 ; Put address of subtrahend table in A1.
move.l (a1)+, d0 ; Put first subtrahend in D0.
move.b #$30, d2 ; Initialize subtractions counter to ASCII zero.
get_sign:
tst.l d1 ; Is binary number positive, negative or zero?
beq.s zero_passed ; Branch if number is 0.
bpl.s positive ; Branch if positive.
move.b #$2D, (a0)+ ; Store a minus sign in array "decimal".
neg.l d1 ; Change binary number from neg to pos.
bra.s discard_leading_zeroes
positive: ; Branch to here when number is positive.
move.b #$20, (a0)+ ; Store a space in array decimal.
discard_leading_zeroes: ; Subtract subtrahend from minuend.
sub.l d0, d1 ; Loop till difference is positive,
bpl.s subtract ; indicating that digit is not zero.
add.l d0, d1 ; Restore minuend.
move.l (a1)+, d0 ; Get next subtrahend.
bra.s discard_leading_zeroes
subtract:
addq.b #1, d2 ; Increment subtractions counter.
sub.l d0, d1 ; Subtract subtrahend from D1.
bpl.s subtract ; Loop until D1 becomes negative.
move.b d2, (a0)+ ; Store the ASCII digit in array "decimal".
loop_setup:
add.l d0, d1 ; Restore the minuend.
move.b #$2F, d2 ; Pre-initialize subtractions counter to $30-1.
move.l (a1)+, d0 ; Get next subtrahend.
bne.s subtract ; Loop back until subtrahend = 0.
move.b #0, (a0) ; Terminate decimal string with a null.
rts
zero_passed:
move.b #$20, (a0)+ ; Store a space in array "decimal".
move.b d2, (a0)+ ; Store an ASCII zero in array "decimal".
move.b #0, (a0) ; Terminate ASCII decimal string with a null.
rts
data
subtrahend: dc.l $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710,$3E8
dc.l $64,$A,$1,$0
heading: dc.b 'PRG_3CP Execution Results',$D,$A,$D,$A,0
part_1_head: dc.b ' Part 1: Conversion Verification',$D,$A,$D,$A,0
sect_1_head: dc.b ' Section 1: Positive Number Conversion',$D,$A,$D,$A,0
sect_2_head: dc.b ' Section 2: Negative Number Conversion',$D,$A,$D,$A,0
sect_3_head: dc.b ' Section 3: Converson for Zero',$D,$A,$D,$A,0
division_head: dc.b ' Decimal value by repeated division method: ',0
subtract_head: dc.b $D,$A
dc.b ' Decimal value by repeated subtraction method: ',0
part_2_head: dc.b ' Part 2: Execution Speed Results',$D,$A,$D,$A,0
time_head: dc.b ' Times for 1000 executions of each conversion method.',0
div_time_head: dc.b ' Elapsed time for repeated division method: ',0
sub_time_head: dc.b ' Elapsed time for repeated subtraction method: ',0
units_label: dc.b ' milliseconds',$D,$A,0
newlines: dc.b $D,$A,$D,$A,0
bss
align ; Align storage on a word boundary.
reversed: ds.l 3 ; Temp buffer for the repeated division method.
decimal: ds.l 3 ; Output buffer, must be NULL terminated.
ds.l 24 ; Program stack, short enough for examination.
stack: ds.l 0 ; Address of program stack.
program_end: ds.l 0 ; Marks the end of program memory.
end
PRG_3CP Execution Results
Part 1: Conversion Verification
Section 1: Positive Number Conversion
Decimal value by repeated division method: 2147483647
Decimal value by repeated subtraction method: 2147483647
Section 2: Negative Number Conversion
Decimal value by repeated division method: -7483647
Decimal value by repeated subtraction method: -7483647
Section 3: Converson for Zero
Decimal value by repeated division method: 0
Decimal value by repeated subtraction method: 0
Part 2: Execution Speed Results
Elapsed time for repeated division method: 4760 milliseconds
Elapsed time for repeated subtraction method: 2440 milliseconds
The repeated division algorithm is much slower than the
repeated subtraction algorithm just because it uses repeated
division. Each division consumes 140 clock periods, but
each subtraction consumes only 8 clock periods. I should
mention that the elapsed time for the repeated division
method can be reduced to 4695 milliseconds with the
following modifications to the algorithm: insert move.w
#10,d4 just before the _get_sign label, then change the two
divu #10,d1 instructions to divu d4,d1.
With these alterations, each division is 4 clock
periods faster. The speed of the repeated division
algorithm can be increased a little more by making the same
changes for the ASCII code conversion as was suggested for
the repeated subtraction algorithm (and implemented in
program 11). I did not make the alterations discussed in
this paragraph because I wanted you to see the repeated
division algorithm as it most often appears in references.
Load and Stay Resident Programs
If only because of precedence, we have come to regard a
particular type of program execution cycle as normal. To
initiate this cycle, a user performs some action that causes
the operating system to load a program into memory and give
processor control to the program. For some finite length of
time, the program is in control of some subset of the
computer's total capacity. Eventually, the program
accomplishes is designed task and returns processor control
to the operating system. The operating system then removes
the program from memory either by actually clearing the part
of memory occupied by the program or simply by "forgetting"
that it is there.
But there is a type of program which does not follow
the normal execution cycle. The execution of programs in
this class may be initiated by the operating system, a user
or another program. One characteristic of these programs
that differentiates them from normal programs is their
length of residency in memory; once loaded, they are not
usually removed for the life of the power-up cycle. In
addition, their execution need not proceed linearly; that
is, once loaded into memory, it may be that only a portion
of the program's instructions will be executed immediately
and the balance may be executed at a later time, or, as is
sometimes the case, a portion of the program may never be
executed.
Some references refer to this class of programs, in the
most general sense, as terminate and stay resident (TSR)
programs. I prefer the more accurate descriptive load and
stay resident (LSR). The word terminate most naturally
connotes end or finish; but, as you shall see, programs of
this class need not terminate during the life of the power-
up cycle.
GEMDOS Function $31
The easiest way to establish a program as type LSR is
to exit the program with GEMDOS function $31 instead of
function $0. For a complete discussion of function $31,
please refer to pages 121-122 of the Internals book. In
short, this function is used to relinquish processor
control, but, simultaneously, it tells the operating system
to keep the program in memory in such a manner that the
program may seize, or be given, control of the processor at
some later time.
At the time that function $31 is executed, it is
possible, and probable, that the program has not completed,
or even initiated its intended function or functions. Most
likely, at some time during the power-up cycle, the program
will be given control of the processor so that it can
perform the function for which it was designed. In the same
manner that we do not think of the operating system as
terminating when it relinquishes control to a program, we
need not consider a program to have terminated when it
executes function $31.
Program 12 illustrates the use of GEMDOS function $31
to establish permanent memory residency. When executed from
the desktop, this program will be loaded into memory, where
it will remain until the computer is powered down. At the
onset of execution, the program prints its starting address
and ending address on the video screen, then waits for a
keypress so that you can write them down.
After you record the addresses, press the Return key.
When the desktop appears, execute ASSEMPRO.PRG and go to the
debugger. Click on the from address button and type the
first address (program_start) on the from address parameter
line. The program's first instruction will appear as the
first line in the output field. There you will see the
second address (program_end) being loaded into register A3.
Use this method of obtaining the location of an LSR
program in memory whenever you wish to examine it via the
debugger or whenever you want to obtain a disassembly
listing of this type of program, especially after certain
portions, or the entire program has been executed. After
you gain sufficient knowledge, you will be able to attach
appropriate instructions to LSR programs written by others
so that you can locate them in memory with the debugger.
Program 12. Establishing a LSR program with GEMDOS
function $31. Note the warning given.
; Program Name: PRG_4AP.S
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Program Function:
; This program simply establishes itself in memory as a Load and Stay
; Resident (LSR) program and prints its location to the video screen. The
; addresses printed are the program start address (not the basepage address)
; and the program end address.
; Program Purpose:
; To illustrate the role of GEMDOS function $31 in establishing a
; program as LSR.
; Execution Instructions:
; Execute the program from the desktop. When the addresses appear on
; the screen, write them down. Terminate execution by pressing the Return
; key. In the AssemPro debugger, go to the first address using the
; "from address" function. On the first line in the output field, you
; should see the second address (program_end) being loaded into register A3.
; Once this program has been executed, it will remain in ram memory until
; the computer is rebooted.
; WARNING: If this program or any other LSR program is executed from within
; the AssemPro debugger, upon exit from AssemPro, the operating
; system will not be able to clear the program from memory.
; Because the program will be residing in an area of memory that
; was controlled by AssemPro, the environment will be corrupted.
; You can confirm this by trying to reexecute AssemPro after you
; exit. You will receive a Bus error message.
; Furthermore, you will not be able to assemble a program until
; you reset the system.
; There is only one thing you can do if you execute a LSR program
; from within AssemPro; you must reset the system by turning it
; completely off, then back on. You can try a warm reset, but
; all bets are off if you do that.
program_start: ; Calculate program size and retain result.
lea program_end, a3 ; Fetch address of last memory location occupied
movea.l a3, a4 ; by the program. Copy into scratch register.
suba.l 4(a7), a3 ; Subtract basepage address from program end
; address. After this, the basepage address
; is no longer needed in this program.
fetch_stack_address:
lea stack, a7
print_memory_locations:
lea load_message, a0
bsr.s print_string
lea program_start, a0
move.l a0, d1 ; Transfer to D1 for binary to ASCII
; hexadecimal conversion.
; Note: Above, must load address into an address register first, then move
; to the data register for binary to hexadecimal conversion, in order to
; permit PC-relative assembly.
bsr.s bin_to_hex ; bin_to_hex expects binary number in D1.
lea hexadecimal, a0 ; Print the hexadecimal string.
bsr.s print_string
lea separator, a0 ; Print a separator between addresses.
bsr.s print_string
move.l a4, d1 ; Program end address is in scratch register.
bsr.s bin_to_hex
lea hexadecimal, a0
bsr.s print_string
bsr.s print_newline
wait_for_keypress: ; Give time to write down memory addresses.
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp
; Note: GEMDOS function $31 doesn't need the basepage address.
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
;
; SUBROUTINES
;
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D1. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. Maximum size of the binary number is 32 bits = 8 nibbles.
; The algorithm discards leading zeroes.
; The conversion from binary nibble to hex digit is accomplished by
; extracting the character in the hex table that is located at the position
; defined by the decimal value of the nibble. For example, if the nibble
; is "1111", the decimal value is 15; the 15th element of the hex table is
; the letter F. The location in the table is specified by an offset from
; the address of the first character of the table, which is stored in A1.
; The value of the offset is stored in register D0. The addressing mode
; used to locate the appropriate table entry is "address register indirect
; with offset".
bin_to_hex: ; Expects binary number in D1.
lea hexadecimal, a0 ; A0 is pointer to array "hexadecimal".
tst.l d1 ; Test for contents = 0.
beq.s zero_passed ; Branch if number is 0.
lea hex_table, a1 ; A1 is pointer to array "hex_table".
lea hex_table, a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter for 8 nibbles.
discard_leading_zeroes:
rol.l #4, d1 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
bne.s store_digit ; Branch and store if not leading zero.
dbra d2, discard_leading_zeroes
continue:
rol.l #4, d1 ; Rotate most significant nibble.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, continue ; Continue looping until D2 = -1.
move.b #0, (a0) ; Terminate hexadecimal string with a null.
rts
zero_passed:
move.b #$30, (a0)+ ; Store an ASCII zero in "hexadecimal".
move.b #0, (a0) ; Terminate ASCII hexadecimal string with null.
lea hexadecimal, a0
rts
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
data
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
load_message: dc.b 'Installing PRG_4AP between hex addresses: ',0
separator: dc.b ' - ',0
align
bss
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
ds.l 16
stack: ds.l 1
program_end: ds.l 0
end
Program 12 Execution Results: The addresses you see will probably be very
different.
Installing PRG_4AP between hex addresses: 1CB10 - 1CC56
As you can see, a new number conversion algorithm is
introduced in program 12. The addresses printed by the
program must be shown in ASCII hexadecimal so that they
match the system in which addresses are shown in the
debugger output field. Since the conversion from binary to
hexadecimal is so simple the algorithm is not at all
complex.
The binary number passed to the subroutine is composed
of eight nibbles, each nibble representing a four binary
digit hexadecimal digit. Beginning with the most
significant nibble, each nibble is rotated, in turn, to the
least significant position and transferred to another
register as the least significant nibble of a byte of data.
The least significant byte of the new register is masked
with $0F so that it can be used to fetch the least
significant nibble's ASCII character from an array of
hexadecimal characters. Masking is required because the
smallest portion of data that can be transferred from one
data register to another is one byte. But the upper nibble
of that byte corrupts the element of data needed to access
characters in the table. Therefore, we render that upper
nibble impotent by anding it with 0.
You will find similar algorithms in many of the
references. These algorithms will also show you how to use
the movem.l instruction to save the content of registers
used by the subroutine whenever you find that to be
desirable. I try to avoid that by judicious register
control and variable assignments in my programs.
Confirming the Validity of the Conversion Algorithm
The limited use of the binary to ASCII hexadecimal
algorithm in program 12 did not prove its validity with a
variety of binary values. Program 14 was written to
exercise the algorithm to at least some pertinent degree of
confidence. But program 14 will not function until program
13 is installed as a LSR program. Therefore, program 14
will follow program 13.
Program 13 is designed to provide user traps for
algorithms that will be used in many of the programs to be
introduced. Invoking traps for the most often used routines
will permit those programs to be smaller and less intricate.
Some execution speed will be sacrificed, but the advantages
gained compensate for the loss. In your own programs you
will have to decide when the compromises are acceptable.
Program 13. Using GEMDOS Function $31 to Install Custom
Traps.
; Program Name: TRAPS.S
; Version 1.005
; Assembly Instructions:
; Assemble in PC-relative mode and save with a PRG extension.
; Program Function:
; This is a LSR program that establishes user defined traps. It may be
; executed from the desktop, but you may prefer to copy it to the AUTO
; folder of your boot partition or floppy disk so that it will execute
; automatically during boot.
; NOTE: If the program has been loaded into memory during boot, the
; operating system will not let you remove it from the AUTO folder.
; After an attempt is made to remove the file, an Alert box will
; appear with the message:
; ! An item with this name already exists in the
; directory, or this item is set to Read-only status.
; To remove a program from the AUTO folder after this message appears,
; click once on the file's icon, then select Show Info under the File menu.
; Change the filename's extension from PRG to PRX. The next time the
; system is booted you will be able to remove the file.
; The best method of dealing with programs in the AUTO folder and with
; desk accessories is via a program that permits you to select or deselect
; them from a displayed list at boot. I use MichTron's STSELECT, which
; is one of 21 utilities in their STuff package. When you deselect an AUTO
; folder program, STSELECT changes the program's suffix to PRX. The
; suffix change prevents the program from being executed until its suffix
; is altered manually, or via STSELECT, to PRG.
; The STSELECT utility is especially useful when you are not sure that
; a program in the AUTO folder or a desk accessory will execute properly.
; A malfunction in one of these programs at boot will prevent the system
; from booting, but will not prevent it from trying to boot, thereby putting
; the system in an infinite loop from which it cannot escape. With STSELECT
; you won't have to worry about that problem. If one of the programs disrupts
; the boot process, simply deselect it when STSELECT displays its list.
; The custom traps are installed so that the algorithms here will not
; have to be repeated in all of the programs in the book that use them, not
; because they offer any other advantages. I want you to know this because,
; in general, I prefer in-line code to the invocation of traps, except when
; they offer a clear cut speed advantage to in-line code, as does the
; custom trap #0. This trap is a significant improvement over GEMDOS $20
; when forcing the processor into the supervisor mode.
program_start: ; Calculate program size and retain result.
lea program_end, a3 ; Fetch program end address.
suba.l 4(a7), a3 ; Subtract basepage address.
enter_supervisor_mode:
move.l #0, -(sp) ; The zero turns on supervisor mode.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Go to supervisor mode.
addq.l #6, sp ; Supervisor stack pointer (SSP) returned in D0.
movea.l d0, a5 ; Save SSP in scratch register.
; NOTE: Because the processor is now in supervisor mode, the system trap
; vectors may be referenced directly; that is, loading the starting
; value $80 in an address register and referencing all trap vectors
; as an offset to the starting value using "address register indirect
; with displacement addressing" is not really necessary. I am doing
; that only to show how it is done when it is necessary. A faster
; method is shown in the program CUSTOM.S.
install_trap_0_routine: ; Trap #0 = set supervisor mode.
lea $80, a0 ; Fetch trap #0 vector address.
lea trap_0_routine, a1 ; Fetch custom trap #0 vector.
move.l a1, (a0) ; Store custom trap #0 vector.
install_trap_3_routine: ; Trap #3 = get time (contents of $4BA).
lea trap_3_routine, a1 ; Fetch custom trap #3 vector.
move.l a1, $C(a0) ; Store custom trap #3 vector.
install_trap_4_routine: ; Trap #4 = convert binary number in D1 to
lea trap_4_routine, a1 ; ASCII decimal, suppressing leading zeroes.
move.l a1, $10(a0) ; Returns address of decimal string in A0.
install_trap_5_routine: ; Trap #5 = convert binary number in D1 to
lea trap_5_routine, a1 ; ASCII hexadecimal, suppressing leading zeroes.
move.l a1, $14(a0) ; Returns address of hexadecimal string in A0.
install_trap_6_routine: ; Trap #6 accepts a program's end address in
lea trap_6_routine, a1 ; A0 and basepage address in A1. It computes
move.l a1, $18(a0) ; the program size and returns unused memory.
install_trap_7_routine: ; Trap #7 = convert binary number in D1 to
lea trap_7_routine, a1 ; ASCII hexadecimal, retaining leading zeroes.
move.l a1, $1C(a0) ; Returns address of hexadecimal string in A0.
install_trap_8_routine: ; Terminate with GEMDOS $0 after a keypress
lea trap_8_routine, a1 ; if program was not spawned by SPEEDTST.TTP,
move.l a1, $20(a0) ; else terminate with GEMDOS $4C.
enter_user_mode:
pea (a5) ; Restore supervisor stack pointer.
move.w #$20, -(sp) ; Function = super = GEMDOS $20.
trap #1 ; Go to user mode.
addq.l #6, sp ; Reset stack pointer to top of stack.
relinquish_processor_control: ; Maintain memory residency.
move.w #0, -(sp) ; See page 121 of Internals book.
move.l a3, -(sp) ; Program size.
move.w #$31, -(sp) ; Function = ptermres = GEMDOS $31.
trap #1
trap_0_routine: ; Sets bit 13 of status register.
; When trap #0 is invoked, the CPU pushes the calling program's return
; address onto the supervisor stack, then it pushes the calling program's
; status register contents onto the supervisor stack.
; This custom trap #0 handler sets bit #13 of the calling program's status
; register. The bset instruction can be used to set a bit in the first
; byte of the word on the top of the stack. Bit #13 of the status register
; is bit #5 of that byte. Note: to set a bit means to make it equal to 1.
; When the rte instruction is executed, the CPU will return to the calling
; program with the altered copy of its status register, and the calling
; program will now be executing in supervisor mode.
; Register A7 will contain the address of the supervisor stack. The calling
; program now has complete control of the system. But a decision about
; which stack is to be used must be made.
; The calling program can choose to continue with A7 pointing to the
; supervisor stack, or it can save the contents of A7 = SSP and load the
; address of a user stack into A7. Processing can continue in that manner
; until it is necessary to return to user mode. The return to user mode
; can be accomplished by reloading A7 with the SSP, then by resetting bit
; #13 of the status register. Note: resetting a bit means to make it equal
; to 0.
bset #5, (sp) ; Places calling program in supervisor mode.
rte
trap_3_routine: ; Returns contents of $4BA in D0.
; The get_time subroutine obtains the current value of the system variable
; _hz_200 (memory location $4BA). This variable is incremented every 200
; hertz, which means that the period between increments is 5 milliseconds
; (1/200 = .005). In turn, this means that the variable measures time with
; a resolution of 5 milliseconds.
; Use this subroutine to measure the elapsed time of an event as follows:
; 1. Read and store the content of $4BA at the beginning of the event.
; 2. At the conclusion of the event, read the content of $4BA again.
; 3. Subtract the first value from the second. This yields the number of
; 5 millisecond intervals which occurred during the event.
; 4. Multiply the difference by 5 to convert the elapsed time to milliseconds.
move.l $4BA, d0
rte
trap_4_routine: ; Binary to ASCII decimal conversion.
; This is a binary to ASCII decimal conversion subroutine. It converts a
; 32-bit binary number in register D1 to an ASCII string that is stored in
; an array called "decimal".
; The subroutine uses an algorithm based on "repeated subtraction". The
; subtrahends are powers of ten which range from 10^1 to 10^9. These values
; were chosen to accommodate up to ten digit integers. The binary value
; passed to the subroutine is the initial current minuend. After a decimal
; digit is extracted from a current minuend, a new current minuend is
; produced.
; During the subtraction process, the minuend is gradually reduced to a
; negative number. The number of times that each power of ten can be
; subtracted from a current minuend before the minuend becomes negative
; yields a significant digit for that power of ten.
; After the current minuend becomes negative a "new" minuend is formed by
; adding the current subtrahend to the negative value. The process
; continues until the current subtrahend is 0.
; Register D2 is the subtractions counter. In it is accumulated the number
; of times that a subtrahend can be subtracted from a minuend before the
; minuend becomes negative. At the start of the subroutine D2 is initialized
; to the ASCII code for decimal zero, therefore, as the first digit is
; extracted, it is the digit's ASCII code which is accumulated in D2.
; But observe that D2 is initialized to $2F in the section of the routine
; labeled "loop_setup". The reason: after registers are initialized in the
; loop_setup section, execution branches to the section labeled "subtract".
; The first instruction in that section adds 1 to the value in D2.
; I chose to add 1 to the subtractions counter at that particular location
; because it was a convenient point in the algorithm to do so upon exit from
; the "discard leading zeroes" loop. Upon exit from that loop, a valid
; subtraction has already been performed, therefore, it must be accumulated.
; Since D2 contains the initialized value $30 at that point in the execution,
; adding 1 puts the correct count in D2.
; Because I want to branch to the same section upon exit from the loop setup
; section, I must pre-initialize D2 to $2F, which is one less than $30.
; Then, after 1 is added to D2 via the first instruction in the subtract
; section, the subtractions counter is fully initialized to $30, as it
; should be.
; As each decimal digit is extracted from D1, its ASCII code is accumulated
; as part of a string in the array called "decimal".
; The algorithm discards leading zeroes using a loop that subtracts
; subtrahends from the number in D1, beginning with the largest, until a
; subtrahend which does immediately yield a negative difference is detected.
bin_to_dec_2:
lea decimal, a0 ; Put address of array "decimal" in A0.
lea subtrahend, a1 ; Put address of subtrahend table in A1.
move.l (a1)+, d0 ; Put first subtrahend in D0.
move.b #$30, d2 ; Initialize subtractions counter to ASCII zero.
get_sign:
tst.l d1 ; Is binary number positive, negative or zero?
beq.s zero_passed ; Branch if number is 0.
bpl.s positive ; Branch if positive.
move.b #$2D, (a0)+ ; Store a minus sign in array "decimal".
neg.l d1 ; Change binary number from neg to pos.
bra.s discard_leading_zeroes
positive: ; Branch to here when number is positive.
move.b #$20, (a0)+ ; Store a space in array decimal.
discard_leading_zeroes: ; Subtract subtrahend from minuend.
sub.l d0, d1 ; Loop till difference is positive,
bpl.s subtract ; indicating that digit is not zero.
add.l d0, d1 ; Restore minuend.
move.l (a1)+, d0 ; Get next subtrahend.
bra.s discard_leading_zeroes
subtract:
addq.b #1, d2 ; Increment subtractions counter.
sub.l d0, d1 ; Subtract subtrahend from D1.
bpl.s subtract ; Loop until D1 becomes negative.
move.b d2, (a0)+ ; Store the ASCII digit in array "decimal".
loop_setup:
add.l d0, d1 ; Restore the minuend.
move.b #$2F, d2 ; Pre-initialize subtractions counter to $30-1.
move.l (a1)+, d0 ; Get next subtrahend.
bne.s subtract ; Loop back until subtrahend = 0.
move.b #0, (a0) ; Terminate decimal string with a null.
lea decimal, a0
rte
zero_passed:
move.b #$20, (a0)+ ; Store a space in array "decimal".
move.b d2, (a0)+ ; Store an ASCII zero in array "decimal".
move.b #0, (a0) ; Terminate ASCII decimal string with a null.
lea decimal, a0
rte
trap_5_routine:
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D1. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. Maximum size of the binary number is 32 bits = 8 nibbles.
; The algorithm discards leading zeroes.
; The conversion from binary nibble to hex digit is accomplished by
; extracting the character in the hex table that is located at the position
; defined by the decimal value of the nibble. For example, if the nibble
; is "1111", the decimal value is 15; the 15th element of the hex table is
; the letter F. The location in the table is specified by an offset from
; the address of the first character of the table, which is stored in A1.
; The value of the offset is stored in register D0. The addressing mode
; used to locate the appropriate table entry is "address register indirect
; with offset".
bin_to_hex:
lea hexadecimal, a0 ; A0 is pointer to array "hexadecimal".
tst.l d1 ; Test for contents = 0.
beq.s _zero_passed ; Branch if number is 0.
lea hex_table, a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter for 8 nibbles.
omit_leading_zeroes:
rol.l #4, d1 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
bne.s store_digit ; Branch if digit is not zero.
dbra d2, omit_leading_zeroes
continue:
rol.l #4, d1 ; Rotate most significant nibble.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
store_digit:
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, continue ; Continue looping until D2 = -1.
move.b #0, (a0) ; Terminate hexadecimal string with a null.
lea hexadecimal, a0
rte
_zero_passed:
move.b #$30, (a0)+ ; Store an ASCII zero in "hexadecimal".
move.b #0, (a0) ; Terminate ASCII hexadecimal string with null.
lea hexadecimal, a0
rte
trap_6_routine:
; Expects program_end address in A0 and basepage address in A1.
; This routine does several things. First, using the values passed in
; registers A0 and A1, it calculates the invoking program's size and
; returns excess memory to the operating system.
; Then it checks to see if the process invoking trap #6 was spawned. If
; it was, then a variable is set to true; if not, then the variable is set
; to false. In addition, if the process was spawned, the after_load time
; is saved in a variable so that it can be returned by trap #8.
calculate_program_size:
suba.l a1, a0
prepare_stack_to_return_unused_memory:
pea (a0) ; Push program length.
pea (a1) ; Push basepage address.
move.l #$4A0000, -(sp) ; Function = m_shrink = GEMDOS $4A.
spawned_test:
lea spawned, a0 ; Fetch boolean variable's address.
move.l $2C(a1), a1 ; Fetch environmental string address.
cmpi.l #'TERM', (a1) ; Compare environmental string to "TERM".
seq (a0) ; Set spawned to FF if program was spawned.
bne.s return_memory ; Branch if "TERM" string not present.
save_after_load_time:
lea after_load_time, a0 ; Fetch variable's address.
move.w d0, (a0)
return_memory:
trap #1 ; Invoke GEMDOS exception.
lea $C(a7), sp ; Reset stack pointer to top of stack.
rte
trap_7_routine:
; The binary to ASCII hexadecimal conversion routine expects a number to be
; passed as a longword in register D1. Beginning with the most significant
; nibble (a nibble = four bits), each nibble is converted to its ASCII
; hexadecimal equivalent and stored in "hexadecimal", a null terminated
; buffer. The leading zeroes are retained in this routine.
bin_to_hex_with_zeroes:
lea hexadecimal, a0 ; A0 is pointer to array "hexadecimal".
tst.l d1 ; Test for contents = 0.
beq.s _zero__passed ; Branch if number is 0.
lea hex_table, a1 ; A1 is pointer to array "hex_table".
moveq #7, d2 ; D2 is the loop counter.
moveq #0, d0 ; D0 is not zero. That's proved in a later
; program, so it must be cleared before use.
rotate_and_convert:
rol.l #4, d1 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d1, d0 ; Copy least significant byte of D1 to D0.
andi.b #$F, d0 ; Mask out most significant nibble of D0.
move.b 0(a1,d0.w), (a0)+ ; Store ASCII hexadecimal digit in buffer.
dbra d2, rotate_and_convert
move.b #0, (a0) ; Terminate hexadecimal string with a null.
lea hexadecimal, a0 ; Return address of hex string in A0.
rte
_zero__passed:
move.b #$30, (a0)+ ; Store an ASCII zero in "hexadecimal".
move.b #0, (a0) ; Terminate ASCII hexadecimal string with null.
lea hexadecimal, a0
rte
trap_8_routine:
; When custom trap #8 is invoked by a program, the state of the boolean
; variable "spawned" is tested. If the state is true (equal to FF), the
; program invoking trap #8 is terminated with GEMDOS function $4C and the
; after-load time, which was saved by custom trap #6, is returned to the
; parent program in D0.
; If the state of "spawned" is false, GEMDOS function $8 is executed so
; that execution of the spawned program will pause for a keypress. When
; the keypress is received, GEMDOS function $0 is executed.
; In this manner, custom trap #8, working in conjunction with custom trap
; #6, eliminates the "wait for keypress" algorithm automatically when a
; program is spawned by one of the speed testing programs. This prevents
; the spawned program's computed execution time from being corrupted with
; a time period that involves a wait for keyboard input.
lea spawned, a0 ; Fetch address of variable.
tst.b (a0) ; Spawned test.
beq.s not_spawned ; Branch if program not spawned.
lea after_load_time, a0 ; Pass after-load time to parent.
move.w (a0), -(sp) ; Push after-load time.
move.w #$4C, -(sp) ; Function = p_term = GEMDOS $4C.
trap #1 ; Terminate and return to parent.
not_spawned:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp ; Reposition stack pointer at top of stack.
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; Terminate and return to desktop.
data
subtrahend: dc.l $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710
dc.l $3E8,$64,$A,$1,$0
hex_table: dc.b '0123456789ABCDEF'
spawned: dc.b $0
bss
align ; Align storage on a word boundary.
after_load_time: ds.w 1 ; Spawned program's after load time.
decimal: ds.l 3 ; Output buffer. Must be NULL terminated.
hexadecimal: ds.l 3 ; Output buffer. Must be NULL terminated.
program_end: ds.l 0
end
Program 14. A program to test the validity of the binary to
hexadecimal algorithm. Simultaneously, this program
confirms that the algorithm has been installed as a custom
trap by program 13.
; Program Name: HEX_TEST.S
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Program Function:
; Used to verify the accuracy of the binary to ASCII hexadecimal
; algorithm used in PRG_4AP.TOS and TRAPS.PRG. The hexadecimal algorithm
; is invoked with a trap #5 instruction.
; Execution Instructions:
; Execute from the desktop. Compare the inputs to the conversion
; algorithm to its outputs. Press the Return key to terminate the program.
; NOTE: The custom traps must be resident when this program is executed.
; This means that program TRAPS.PRG must be executed from the AUTO
; folder or from the desktop before program HEX_TEST.TOS is executed.
release_excess_memory:
lea program_end, a0 ; Put "end of program" address in A0.
movea.l 4(a7), a1 ; Put "basepage" address in A1.
trap #6 ; Calculate program size and release memory.
lea stack, a7 ; Point A7 to this program's stack.
mainline: ; Marks the beginning of program proper.
lea heading, a0 ; Print heading for program's output.
bsr.s print_string
test_1:
lea string_1, a3
lea value_1, a4
bsr.s test_routine
test_2:
lea string_2, a3
lea value_2, a4
bsr.s test_routine
test_3:
lea string_3, a3
lea value_3, a4
bsr.s test_routine
test_4:
lea string_4, a3
lea value_4, a4
bsr.s test_routine
wait_for_keypress:
move.w #8, -(sp) ; Function = c_necin = GEMDOS $8.
trap #1 ; GEMDOS call.
addq.l #2, sp ; Reposition stack pointer at top of stack.
terminate:
move.w #0, -(sp) ; Function = p_term_old = GEMDOS $0.
trap #1 ; GEMDOS call.
;
; SUBROUTINES
;
test_routine:
lea input_msg, a0
bsr.s print_string
lea (a3), a0
bsr.s print_string
bsr.s print_newline
lea output_msg, a0
bsr.s print_string
move.l (a4), d1
trap #5 ; Returns address of "hexadecimal" in A0.
bsr.s print_string
bsr.s print_newline
rts
print_string: ; Expects address of string to be in A0.
pea (a0) ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp ; Reset stack pointer to top of stack.
rts
print_newline: ; Prints a carriage return and linefeed.
pea newline ; Push address of string onto stack.
move.w #9, -(sp) ; Function = c_conws = GEMDOS $9.
trap #1 ; GEMDOS call
addq.l #6, sp
rts
data
string_1: dc.b 'ABCDEF01',0
value_1: dc.l $ABCDEF01
string_2: dc.b '23456789',0
value_2: dc.l $23456789
string_3: dc.b '00FC1000',0
value_3: dc.l $00FC1000
string_4: dc.b '00000000',0
value_4: dc.l $00000000
newline: dc.b $D,$A,0
heading: dc.b 'HEX_TEST Execution Results',$D,$A,$D,$A,0
input_msg: dc.b ' Input to algorithm: $',0
output_msg: dc.b ' Ouput from algorithm: $',0
align
bss
ds.l 16
stack: ds.l 1
program_end: ds.l 0
end
HEX_TEST Execution Results
Input to algorithm: $ABCDEF01
Ouput from algorithm: $ABCDEF01
Input to algorithm: $23456789
Ouput from algorithm: $23456789
Input to algorithm: $00FC1000
Ouput from algorithm: $FC1000
Input to algorithm: $00000000
Ouput from algorithm: $0
Conclusion
As you have seen so far, an initialization sequence for
ST programs can range from nothing at all to those discussed
in this chapter, which are not in the least intricate, but
which can be complicated by an association with the method
used to relinquish processor control. I shall get to the
more complicated initialization procedures after suitable
preparation.
Program 13 represents an alteration to the operating
system environment because it establishes algorithms which
are treated as operating system commands by other
algorithms. As such, it can be considered to be a set of
initial conditions which supplements the unblemished
operating system subenvironment. Indeed, its presence in
memory is a prerequisite to the execution of program 14.
Other programs that are executed at boot behave
similarly to configure the operating system environment, the
execution environment or both. Such programs may be located
in the AUTO folder or in the root directory of the boot
partition or disk. Remember that there are boot programs
which permit you to choose a set of programs to be executed
during boot.